"""
WebService Controller

Main controller for managing WebService CRD lifecycle including Deployment,
Service, and Ingress resources.
"""

import kopf
from datetime import datetime
from typing import Dict, Any, Optional

from kopf import Body
from logging import Logger

from .api import WebService, WebServicePhase, DeploymentStatus, ServiceStatus, IngressStatus
from .deployment_manager import DeploymentManager, DeploymentManagerError
from .service_manager import ServiceManager, ServiceManagerError
from .ingress_manager import IngressManager, IngressManagerError
from .namespace_manager import NamespaceManager, NamespaceManagerError
from .resource_quota_manager import ResourceQuotaManager, ResourceQuotaManagerError
from common import consts
from utils.spec import SpecError


class WebServiceController:
    """Controller for WebService CRD"""

    def __init__(self, namespace: str, logger: Logger):
        """Initialize the controller with all necessary managers"""
        self.namespace = namespace
        self.logger = logger

        # Initialize component managers
        self.namespace_manager = NamespaceManager()
        self.deployment_manager = DeploymentManager()
        self.service_manager = ServiceManager()
        self.ingress_manager = IngressManager()
        self.quota_manager = ResourceQuotaManager()

    def create_webservice(self, webservice: WebService) -> Dict[str, Any]:
        """
        Create WebService and all its components
        
        Args:
            webservice: WebService instance
            
        Returns:
            Status update for the WebService
        """
        # WebService CR is in devops-system namespace, but resources are created in webservice.name namespace
        target_namespace = webservice.name
        self.logger.info(f"Creating WebService {webservice.name} (CR in {webservice.namespace}, resources in {target_namespace})")

        # Get parsed spec
        webservice_spec = webservice.get_spec()

        status_update = {
            'phase': WebServicePhase.PENDING.value,
            'observedGeneration': webservice.metadata.get('generation', 1),
            'lastTransitionTime': datetime.utcnow().isoformat() + 'Z',
            'deployment': {
                'name': webservice.get_deployment_name(),
                'namespace': target_namespace,
                'status': DeploymentStatus.PENDING.value
            },
            'service': {
                'name': webservice.get_service_name(),
                'namespace': target_namespace,
                'status': ServiceStatus.PENDING.value
            },
            'ingress': {
                'name': webservice.get_ingress_name(),
                'namespace': target_namespace,
                'status': IngressStatus.PENDING.value
            }
        }

        try:
            # Ensure target namespace exists first
            self.namespace_manager.ensure_namespace_exists(target_namespace)

            # Create ResourceQuota first
            self.quota_manager.create_resource_quota(webservice_spec, webservice.name, target_namespace, webservice)
            self.logger.info(f"Created ResourceQuota for WebService {webservice.name}")

            # Create deployment
            self.deployment_manager.create_deployment(webservice_spec, webservice.name, target_namespace)
            status_update['deployment']['status'] = DeploymentStatus.PENDING.value

            # Create service
            self.service_manager.create_service(webservice_spec, webservice.name, target_namespace)
            status_update['service']['status'] = ServiceStatus.PENDING.value

            # Create ingress
            self.ingress_manager.create_ingress(webservice_spec, webservice.name, target_namespace)
            status_update['ingress']['status'] = IngressStatus.PENDING.value

            self.logger.info(f"Successfully created all components for WebService {webservice.name} in namespace {target_namespace}")

        except (NamespaceManagerError, DeploymentManagerError, ServiceManagerError, IngressManagerError, ResourceQuotaManagerError) as e:
            self.logger.error(f"Failed to create WebService {webservice.name}: {e}")
            status_update['phase'] = WebServicePhase.FAILED.value
            status_update['deployment']['status'] = DeploymentStatus.UNHEALTHY.value
            status_update['service']['status'] = ServiceStatus.UNHEALTHY.value
            status_update['ingress']['status'] = IngressStatus.UNHEALTHY.value

        return status_update

    def update_webservice(self, webservice: WebService) -> Dict[str, Any]:
        """
        Update WebService and all its components
        
        Args:
            webservice: WebService instance
            
        Returns:
            Status update for the WebService
        """
        # WebService CR is in devops-system namespace, but resources are in webservice.name namespace
        target_namespace = webservice.name
        self.logger.info(f"Updating WebService {webservice.name} (CR in {webservice.namespace}, resources in {target_namespace})")

        # Get parsed spec
        webservice_spec = webservice.get_spec()

        status_update = {
            'phase': WebServicePhase.PENDING.value,
            'observedGeneration': webservice.metadata.get('generation', 1),
            'lastTransitionTime': datetime.utcnow().isoformat() + 'Z',
            'deployment': {
                'name': webservice.get_deployment_name(),
                'namespace': target_namespace,
                'status': DeploymentStatus.PENDING.value
            },
            'service': {
                'name': webservice.get_service_name(),
                'namespace': target_namespace,
                'status': ServiceStatus.PENDING.value
            },
            'ingress': {
                'name': webservice.get_ingress_name(),
                'namespace': target_namespace,
                'status': IngressStatus.PENDING.value
            }
        }

        try:
            # Ensure target namespace exists first
            self.namespace_manager.ensure_namespace_exists(target_namespace)

            # Update ResourceQuota first
            self.quota_manager.update_resource_quota(webservice_spec, webservice.name, target_namespace, webservice)
            self.logger.info(f"Updated ResourceQuota for WebService {webservice.name}")

            # Update deployment
            self.deployment_manager.update_deployment(webservice_spec, webservice.name, target_namespace)
            status_update['deployment']['status'] = DeploymentStatus.PENDING.value

            # Update service
            self.service_manager.update_service(webservice_spec, webservice.name, target_namespace)
            status_update['service']['status'] = ServiceStatus.PENDING.value

            # Update ingress
            self.ingress_manager.update_ingress(webservice_spec, webservice.name, target_namespace)
            status_update['ingress']['status'] = IngressStatus.PENDING.value

            self.logger.info(f"Successfully updated all components for WebService {webservice.name} in namespace {target_namespace}")

        except (NamespaceManagerError, DeploymentManagerError, ServiceManagerError, IngressManagerError, ResourceQuotaManagerError) as e:
            self.logger.error(f"Failed to update WebService {webservice.name}: {e}")
            status_update['phase'] = WebServicePhase.FAILED.value
            status_update['deployment']['status'] = DeploymentStatus.UNHEALTHY.value
            status_update['service']['status'] = ServiceStatus.UNHEALTHY.value
            status_update['ingress']['status'] = IngressStatus.UNHEALTHY.value

        return status_update

    def delete_webservice(self, webservice: WebService) -> bool:
        """
        Delete WebService and all its components
        
        Args:
            webservice: WebService instance
            
        Returns:
            True if deletion was successful
        """
        # WebService CR is in devops-system namespace, but resources are in webservice.name namespace
        target_namespace = webservice.name
        self.logger.info(f"Deleting WebService {webservice.name} (CR in {webservice.namespace}, resources in {target_namespace})")

        # # Set status to Deleting
        # try:
        #     self._update_webservice_status(webservice, WebServicePhase.DELETING.value, "WebService deletion started")
        # except Exception as e:
        #     self.logger.warning(f"Failed to update WebService status to Deleting: {e}")

        success = True

        try:
            # Delete ingress first
            self.ingress_manager.delete_ingress(webservice.name, target_namespace)
        except IngressManagerError as e:
            self.logger.error(f"Failed to delete ingress for WebService {webservice.name}: {e}")
            success = False

        try:
            # Delete service
            self.service_manager.delete_service(webservice.name, target_namespace)
        except ServiceManagerError as e:
            self.logger.error(f"Failed to delete service for WebService {webservice.name}: {e}")
            success = False

        try:
            # Delete deployment
            self.deployment_manager.delete_deployment(webservice.name, target_namespace)
        except DeploymentManagerError as e:
            self.logger.error(f"Failed to delete deployment for WebService {webservice.name}: {e}")
            success = False

        try:
            # Delete ResourceQuota last
            self.quota_manager.delete_resource_quota(webservice.name, target_namespace, webservice)
            self.logger.info(f"Deleted ResourceQuota for WebService {webservice.name}")
        except ResourceQuotaManagerError as e:
            self.logger.error(f"Failed to delete ResourceQuota for WebService {webservice.name}: {e}")
            success = False

        # Delete namespace after all resources are deleted
        try:
            self.namespace_manager.delete_namespace(target_namespace)
            self.logger.info(f"Deleted namespace {target_namespace} for WebService {webservice.name}")
        except NamespaceManagerError as e:
            self.logger.error(f"Failed to delete namespace {target_namespace} for WebService {webservice.name}: {e}")
            success = False

        if success:
            self.logger.info(f"Successfully deleted all components and namespace for WebService {webservice.name}")
        else:
            self.logger.warning(f"Some components or namespace failed to delete for WebService {webservice.name}")

        return success

    def get_webservice_status(self, webservice: WebService) -> Dict[str, Any]:
        """
        Get current status of WebService and all its components
        
        Args:
            webservice: WebService instance
            
        Returns:
            Current status of the WebService
        """
        # WebService CR is in devops-system namespace, but resources are in webservice.name namespace
        target_namespace = webservice.name
        
        # Get status of each component
        deployment_status = self.deployment_manager.get_deployment_status(webservice.name, target_namespace)
        service_status = self.service_manager.get_service_status(webservice.name, target_namespace)
        ingress_status = self.ingress_manager.get_ingress_status(webservice.name, target_namespace)

        # Determine overall phase
        overall_phase = self._determine_overall_phase(deployment_status, service_status, ingress_status)

        status = {
            'phase': overall_phase,
            'observedGeneration': webservice.metadata.get('generation', 1),
            'lastTransitionTime': datetime.utcnow().isoformat() + 'Z',
            'deployment': {
                'name': webservice.get_deployment_name(),
                'namespace': target_namespace,
                'status': deployment_status
            },
            'service': {
                'name': webservice.get_service_name(),
                'namespace': target_namespace,
                'status': service_status
            },
            'ingress': {
                'name': webservice.get_ingress_name(),
                'namespace': target_namespace,
                'status': ingress_status
            }
        }

        return status

    def _determine_overall_phase(self, deployment_status: str, service_status: str, ingress_status: str) -> str:
        """
        Determine overall WebService phase based on component statuses
        
        Args:
            deployment_status: Deployment status
            service_status: Service status
            ingress_status: Ingress status
            
        Returns:
            Overall phase of the WebService
        """
        # If any component failed, overall state is failed
        if any(status in [DeploymentStatus.UNHEALTHY.value, DeploymentStatus.UNKNOWN.value,
                          ServiceStatus.UNHEALTHY.value, ServiceStatus.UNKNOWN.value,
                          IngressStatus.UNHEALTHY.value, IngressStatus.UNKNOWN.value]
               for status in [deployment_status, service_status, ingress_status]):
            return WebServicePhase.FAILED.value

        # If all components are healthy, overall state is running
        if (deployment_status == DeploymentStatus.HEALTHY.value and
                service_status == ServiceStatus.HEALTHY.value and
                ingress_status == IngressStatus.HEALTHY.value):
            return WebServicePhase.RUNNING.value

        # Other cases are pending
        return WebServicePhase.PENDING.value

    def recover_webservice(self, webservice: WebService) -> Dict[str, Any]:
        """
        Recover WebService by recreating failed components
        
        Args:
            webservice: WebService instance
            
        Returns:
            Status update for the WebService
        """
        # WebService CR is in devops-system namespace, but resources are in webservice.name namespace
        target_namespace = webservice.name
        self.logger.info(f"Starting recovery for WebService {webservice.name} (CR in {webservice.namespace}, resources in {target_namespace})")

        # Get parsed spec
        webservice_spec = webservice.get_spec()

        # Get current status of each component
        deployment_status = self.deployment_manager.get_deployment_status(webservice.name, target_namespace)
        service_status = self.service_manager.get_service_status(webservice.name, target_namespace)
        ingress_status = self.ingress_manager.get_ingress_status(webservice.name, target_namespace)

        status_update = {
            'phase': WebServicePhase.PENDING.value,
            'observedGeneration': webservice.metadata.get('generation', 1),
            'lastTransitionTime': datetime.utcnow().isoformat() + 'Z',
            'deployment': {
                'name': webservice.get_deployment_name(),
                'namespace': target_namespace,
                'status': deployment_status
            },
            'service': {
                'name': webservice.get_service_name(),
                'namespace': target_namespace,
                'status': service_status
            },
            'ingress': {
                'name': webservice.get_ingress_name(),
                'namespace': target_namespace,
                'status': ingress_status
            }
        }

        recovery_actions = []

        # Check deployment status and recover if needed
        if self._should_recreate_deployment(deployment_status):
            recovery_actions.append("deployment")
            try:
                self.logger.info(f"Recreating deployment for WebService {webservice.name}")
                self.deployment_manager.delete_deployment(webservice.name, target_namespace)
                self.deployment_manager.create_deployment(webservice_spec, webservice.name, target_namespace)
                status_update['deployment']['status'] = DeploymentStatus.PENDING.value
                self.logger.info(f"Successfully recreated deployment for WebService {webservice.name}")
            except Exception as e:
                self.logger.error(f"Failed to recreate deployment for WebService {webservice.name}: {e}")
                status_update['deployment']['status'] = DeploymentStatus.UNHEALTHY.value

        # Check service status and recover if needed
        if self._should_recreate_service(service_status):
            recovery_actions.append("service")
            try:
                self.logger.info(f"Recreating service for WebService {webservice.name}")
                self.service_manager.delete_service(webservice.name, target_namespace)
                self.service_manager.create_service(webservice_spec, webservice.name, target_namespace)
                status_update['service']['status'] = ServiceStatus.PENDING.value
                self.logger.info(f"Successfully recreated service for WebService {webservice.name}")
            except Exception as e:
                self.logger.error(f"Failed to recreate service for WebService {webservice.name}: {e}")
                status_update['service']['status'] = ServiceStatus.UNHEALTHY.value

        # Check ingress status and recover if needed
        if self._should_recreate_ingress(ingress_status):
            recovery_actions.append("ingress")
            try:
                self.logger.info(f"Recreating ingress for WebService {webservice.name}")
                self.ingress_manager.delete_ingress(webservice.name, target_namespace)
                self.ingress_manager.create_ingress(webservice_spec, webservice.name, target_namespace)
                status_update['ingress']['status'] = IngressStatus.PENDING.value
                self.logger.info(f"Successfully recreated ingress for WebService {webservice.name}")
            except Exception as e:
                self.logger.error(f"Failed to recreate ingress for WebService {webservice.name}: {e}")
                status_update['ingress']['status'] = IngressStatus.UNHEALTHY.value

        if recovery_actions:
            self.logger.info(f"Recovery actions taken for WebService {webservice.name}: {', '.join(recovery_actions)}")
        else:
            self.logger.info(f"No recovery actions needed for WebService {webservice.name}")

        return status_update

    def _should_recreate_deployment(self, status: str) -> bool:
        """Check if deployment should be recreated based on status"""
        return status in [
            DeploymentStatus.UNHEALTHY.value,
            DeploymentStatus.UNKNOWN.value
        ]

    def _should_recreate_service(self, status: str) -> bool:
        """Check if service should be recreated based on status"""
        return status in [
            ServiceStatus.UNHEALTHY.value,
            ServiceStatus.UNKNOWN.value
        ]

    def _should_recreate_ingress(self, status: str) -> bool:
        """Check if ingress should be recreated based on status"""
        return status in [
            IngressStatus.UNHEALTHY.value,
            IngressStatus.UNKNOWN.value
        ]


# Kopf event handlers
@kopf.on.create(group=consts.GROUP, version=consts.VERSION, kind=consts.WEB_SERVICE_KIND)
def on_webservice_created(name: str, namespace: Optional[str], body: Body, logger: Logger, **kwargs):
    """Handle WebService creation"""
    logger.info(f"Newly created WebService resource named {name} in namespace {namespace}, start reconciling...")

    webservice = WebService(body)

    try:
        webservice.parse_spec()
        webservice.get_spec().validate()
    except SpecError as e:
        _update_error_status(webservice, "InvalidSpec", str(e))
        raise kopf.TemporaryError(f"Error found in WebService spec: {e}")

    webservice.logging(logger)

    try:
        # Create controller
        controller = WebServiceController(namespace, logger)

        # Create WebService and all components
        status_update = controller.create_webservice(webservice)
        webservice.update_status(status_update)

        logger.info(f"WebService {name} created successfully")

    except Exception as e:
        _update_error_status(webservice, "CreationFailed", str(e))
        raise


@kopf.on.update(group=consts.GROUP, version=consts.VERSION, kind=consts.WEB_SERVICE_KIND)
def on_webservice_updated(name: str, namespace: Optional[str], body: Body, logger: Logger, **kwargs):
    """Handle WebService updates"""
    logger.info(f"WebService {name} in namespace {namespace} was updated, start reconciling...")

    webservice = WebService(body)

    try:
        webservice.parse_spec()
        webservice.get_spec().validate()
    except SpecError as e:
        _update_error_status(webservice, "InvalidSpec", str(e))
        raise kopf.TemporaryError(f"Error found in WebService spec: {e}")

    webservice.logging(logger)

    try:
        # Create controller
        controller = WebServiceController(namespace, logger)

        # Update WebService and all components
        status_update = controller.update_webservice(webservice)
        webservice.update_status(status_update)

        logger.info(f"WebService {name} updated successfully")

    except Exception as e:
        _update_error_status(webservice, "UpdateFailed", str(e))
        raise


@kopf.on.delete(group=consts.GROUP, version=consts.VERSION, kind=consts.WEB_SERVICE_KIND)
def on_webservice_deleted(name: str, namespace: Optional[str], body: Body, logger: Logger, **kwargs):
    """Handle WebService deletion"""
    logger.info(f"WebService {name} in namespace {namespace} was deleted, start cleanup...")

    webservice = WebService(body)
    webservice.logging(logger)

    try:
        # Create controller for cleanup
        controller = WebServiceController(namespace, logger)

        # Delete WebService and all components
        controller.delete_webservice(webservice)

        logger.info(f"WebService {name} cleanup completed")

    except Exception as e:
        logger.error(f"Failed to cleanup WebService {name}: {e}")
        # Don't re-raise to avoid blocking deletion


@kopf.timer(group=consts.GROUP, version=consts.VERSION, kind=consts.WEB_SERVICE_KIND, interval=1)
def monitor_webservice_status(name: str, namespace: Optional[str], body: Body, logger: Logger, **kwargs):
    """Monitor WebService status periodically"""
    webservice = WebService(body)

    # Skip monitoring if WebService is in Deleting phase
    current_phase = webservice.status.get('phase')
    if current_phase == WebServicePhase.DELETING.value:
        logger.debug(f"Skipping status monitoring for WebService {name} in Deleting phase")
        return

    try:
        # Create controller
        controller = WebServiceController(namespace, logger)

        # Get current status
        current_status = controller.get_webservice_status(webservice)

        # Check if status has changed by comparing key fields
        status_changed = False
        current_phase = current_status.get('phase')
        current_deployment_status = current_status.get('deployment', {}).get('status')
        current_service_status = current_status.get('service', {}).get('status')
        current_ingress_status = current_status.get('ingress', {}).get('status')

        existing_phase = webservice.status.get('phase')
        existing_deployment_status = webservice.status.get('deployment', {}).get('status')
        existing_service_status = webservice.status.get('service', {}).get('status')
        existing_ingress_status = webservice.status.get('ingress', {}).get('status')

        if (current_phase != existing_phase or
                current_deployment_status != existing_deployment_status or
                current_service_status != existing_service_status or
                current_ingress_status != existing_ingress_status):
            status_changed = True

        if status_changed:
            # Normal status update
            logger.info(f"Status changed for WebService {name}: "
                        f"deployment={current_deployment_status}, service={current_service_status}, ingress={current_ingress_status}")
            update_status(webservice, current_status, logger, f"status update for {name}")

    except Exception as e:
        logger.error(f"Failed to monitor WebService {name}: {e}")

    return None


@kopf.timer(group=consts.GROUP, version=consts.VERSION, kind=consts.WEB_SERVICE_KIND, interval=1)
def recover_webservice_resources(name: str, namespace: Optional[str], body: Body, logger: Logger, **kwargs):
    """Recover WebService resources if they are in unhealthy state"""
    webservice = WebService(body)

    # Skip recovery if WebService is in Deleting phase
    current_phase = webservice.status.get('phase')
    if current_phase == WebServicePhase.DELETING.value:
        logger.debug(f"Skipping resource recovery for WebService {name} in Deleting phase")
        return

    try:
        # Parse spec first for recovery operations
        webservice.parse_spec()

        # Create controller
        controller = WebServiceController(namespace, logger)

        # Get current status
        current_status = controller.get_webservice_status(webservice)

        # Check if any component needs recovery
        current_deployment_status = current_status.get('deployment', {}).get('status')
        current_service_status = current_status.get('service', {}).get('status')
        current_ingress_status = current_status.get('ingress', {}).get('status')

        needs_recovery = (
                controller._should_recreate_deployment(current_deployment_status) or
                controller._should_recreate_service(current_service_status) or
                controller._should_recreate_ingress(current_ingress_status)
        )

        if needs_recovery:
            logger.info(f"Detected unhealthy states for WebService {name}: "
                        f"deployment={current_deployment_status}, service={current_service_status}, ingress={current_ingress_status}")

            # Perform recovery
            recovery_status = controller.recover_webservice(webservice)

            # Update WebService status with recovery results
            if update_status(webservice, recovery_status, logger, f"recovery for {name}"):
                logger.info(f"Recovery completed for WebService {name}")
        else:
            logger.debug(f"WebService {name} is healthy, no recovery needed")

    except Exception as e:
        logger.error(f"Failed to recover WebService {name}: {e}")

    return None


def update_status(webservice: WebService, status: Dict[str, Any], logger: Logger, operation: str, max_retries: int = 3,
                  retry_delay: int = 0.1) -> bool:
    """
    Safely update WebService status with retry mechanism
    
    Args:
        webservice: WebService instance
        status: Status to update
        logger: Logger instance
        operation: Description of the operation for logging
        
    Returns:
        True if update was successful, False otherwise
    """
    for attempt in range(max_retries):
        try:
            # Use patch instead of update to avoid field inconsistencies
            webservice.update_status(status)
            return True
        except Exception as e:
            # if "inconsistencies" in str(e).lower():
            #     # If it's an inconsistency error, try to get fresh resource and retry
            #     logger.warning(f"Status inconsistency detected during {operation}, refreshing resource...")
            #     try:
            #         # Get fresh resource state
            #         fresh_webservice = WebService.read(webservice.namespace, webservice.name)
            #         fresh_webservice._patch_status(webservice.namespace, webservice.name, {"status": status})
            #         return True
            #     except Exception as refresh_error:
            #         logger.warning(f"Failed to refresh and update status: {refresh_error}")

            if attempt < max_retries - 1:
                logger.warning(f"Failed to update status during {operation} (attempt {attempt + 1}/{max_retries}): {e}")
                import time
                time.sleep(retry_delay * (attempt + 1))  # Exponential backoff
            else:
                logger.error(f"Failed to update status during {operation} after {max_retries} attempts: {e}")
                return False

    return False


def _update_error_status(webservice: WebService, reason: str, message: str):
    """Update WebService status to error"""
    now = datetime.utcnow().isoformat() + 'Z'
    
    # WebService CR is in devops-system namespace, but resources are in webservice.name namespace
    target_namespace = webservice.name

    status = {
        'phase': WebServicePhase.FAILED.value,
        'observedGeneration': webservice.metadata.get('generation', 1),
        'lastTransitionTime': now,
        'deployment': {
            'name': webservice.get_deployment_name(),
            'namespace': target_namespace,
            'status': DeploymentStatus.UNHEALTHY.value
        },
        'service': {
            'name': webservice.get_service_name(),
            'namespace': target_namespace,
            'status': ServiceStatus.UNHEALTHY.value
        },
        'ingress': {
            'name': webservice.get_ingress_name(),
            'namespace': target_namespace,
            'status': IngressStatus.UNHEALTHY.value
        }
    }

    webservice.update_status(status)
